#ifndef __NETWORKPROCESSOR_H__
#define __NETWORKPROCESSOR_H__

#include "Network.h"
#include "NetworkParam.h"
#include "NetworkSocket.h"

namespace SPlayer
{
    namespace Network
    {
        using namespace System;
        using namespace System::Net;
        using namespace System::Net::Sockets;
        using namespace System::Collections::Generic;
        
        ref class NetworkProcessorEventDispatcher
        {
        public:
            Action ^onParamReceived;              /*! This even will be tiggered when server send its param information to here*/
            Action ^onDataInProgress;             /*! This even will be tiggered whenever there is new block of data is received*/
            Action ^onDataReceiveFinished;        /*! This even will be tiggered when all the blocks of data are received*/
            Action<System::Exception ^> ^onError; /*! Connection failed, unexceped shutdown, etc.*/
            Action ^onClosed;
        };
        ref class NetworkProcessor
        {
        public:
            NetworkProcessor(Socket ^connectedSocket)
            {
                initWithSocket(connectedSocket);
            }
            NetworkProcessor(NetworkSocket ^s)
            {
                if (s->Connected || s->connect())
                {
                    this->initWithSocket(s);
                }
            }
            void initWithSocket(Socket ^connectedSocket)
            {
                socket = connectedSocket;
                
                thread_processData_keepAlive = true;
                t_processData =
                    gcnew Threading::Thread(
                    gcnew Threading::ThreadStart(this, &NetworkProcessor::thread_processData));
                t_processData->Start();
            }
            ~NetworkProcessor()
            {
                if (sendData)
                {
                    delete sendData;
                    sendData = nullptr;
                }
                if (receivedData)
                {
                    delete receivedData;
                    receivedData = nullptr;
                }
                if (sendParams)
                {
                    delete sendParams;
                    sendParams = nullptr;
                }
                if (receivedParams)
                {
                    delete receivedParams;
                    receivedParams = nullptr;
                }
                if (eventDispatcher)
                {
                    delete eventDispatcher;
                    eventDispatcher = nullptr;
                }
                if (socket)
                {
                    delete socket;
                    socket = nullptr;
                }
            }
            !NetworkProcessor()
            {
                if (sendData)
                {
                    delete sendData;
                    sendData = nullptr;
                }
                if (receivedData)
                {
                    delete receivedData;
                    receivedData = nullptr;
                }
                if (sendParams)
                {
                    delete sendParams;
                    sendParams = nullptr;
                }
                if (receivedParams)
                {
                    delete receivedParams;
                    receivedParams = nullptr;
                }
                if (eventDispatcher)
                {
                    delete eventDispatcher;
                    eventDispatcher = nullptr;
                }
                if (socket)
                {
                    delete socket;
                    socket = nullptr;
                }
            }
            /*! @brief Send \a sendParams and \a sendData to server
             *! Server's respone can retrieve by eventDispatcher->onDataInProgress
             *! or eventDispatcher->onDataReceiveFinished
             *! 
             *! Before you call send() or sendAsync() function, you should set at least make sure
             *! \a sendParams is set.
             *! 
             *! If you want to communicate with server, eg, receive respone from server.
             *! You should assain some Action to eventDispatcher.
             *! 
             *! @note Before you call send() or sendAsync() function, you should set at least make sure
             *! \a sendParams is set.
             *! */
            void send()
            {
                int nsent = socket->Send(toArray(sendParams, sendData));
            }

            /*! @brief Send \a sendParams and \a sendData to server asynchronously
            *! Server's respone can retrieve by eventDispatcher->onDataInProgress
            *! or eventDispatcher->onDataReceiveFinished
            *!
            *! Before you call send() or sendAsync() function, you should set at least make sure
            *! \a sendParams is set.
            *!
            *! If you want to communicate with server, eg, receive respone from server.
            *! You should assain some Action to eventDispatcher.
            *! 
            *! @note Before you call send() or sendAsync() function, you should set at least make sure
            *! \a sendParams is set.
            *! */
            void sendAsync()
            {
                auto arr = toArray(sendParams, sendData);
                Net::Sockets::SocketAsyncEventArgs ^e = gcnew Net::Sockets::SocketAsyncEventArgs;
                e->SetBuffer(arr, 0, arr->Length);
                socket->SendAsync(e);
            }
       private:
            array<unsigned char> ^toArray(NetworkParamCollection ^params, array<unsigned char> ^data)
            {
                if (data == nullptr)
                    data = gcnew array<unsigned char>(0);
                auto paramArray = System::Text::Encoding::UTF8->GetBytes(params->toString());

                // calculate length
                int paramArrayLength = paramArray->Length;
                int dataLength = data->Length;

                // calculate offset
                int paramArrayOffset = 0 + sizeof(int);
                int dataLengthOffset = paramArrayOffset + paramArrayLength;
                int dataOffset = dataLengthOffset + sizeof(int);

                // prepare buffer
                auto buffer = gcnew array<unsigned char>(sizeof(int) + paramArrayLength + sizeof(int) + dataLength);

                // copy data
                *interior_ptr<int>(&buffer[0]) = paramArrayLength;
                Array::Copy(paramArray, 0, buffer, paramArrayOffset, paramArray->Length);
                *interior_ptr<int>(&buffer[dataLengthOffset]) = dataLength;
                Array::Copy(data, 0, buffer, dataOffset, data->Length);

                return buffer;
            }
        public:
            /*! @brief wait untill param is received or timeout or error occured
             *! You can pass 0 as \a millionsecondTimeout to check whether param is 
             *! received or not.
             *! 
             *! @param millionsecondTimeout Timeout, -1 for never timeout
             *! @return True : param is received, False : timeout or error
             *! */
            bool waitForParamReceived(int millionsecondTimeout)
            {
                auto start = System::DateTime::Now;
                while (!static_cast<bool>(paramReceived))
                {
                    if (millionsecondTimeout != -1 && (System::DateTime::Now - start).Milliseconds > millionsecondTimeout)
                    {
                        return static_cast<bool>(paramReceived);
                    }
                    Threading::Thread::Sleep(1);
                }
                return true;
            }

            bool waitForDataReceived(int millionsecondTimeout)
            {
                auto start = System::DateTime::Now;
                while (!static_cast<bool>(dataReceived))
                {
                    if (millionsecondTimeout != -1 && (System::DateTime::Now - start).Milliseconds > millionsecondTimeout)
                    {
                        return static_cast<bool>(dataReceived);
                    }
                    Threading::Thread::Sleep(1);
                }
                return true;
            }
            void close()
            {
                this->close(0);
            }
            /*! @brief close the socket, left some time for un-sent data to be sent*/
            void close(int millionsecondTimeout)
            {
                this->thread_processData_keepAlive = false;
                socket->Close(millionsecondTimeout);
            }
        private:
            bool thread_processData_keepAlive = true;
            Threading::Thread ^t_processData = nullptr;
            void thread_processData()
            {
                // receive param array
                try
                {
                    auto lengthArray = gcnew array<unsigned char>(sizeof(int));
                    int nread = socket->Receive(lengthArray);

                    int paramArrayLength = *interior_ptr<int>(&lengthArray[0]);
                    auto paramArray = gcnew array<unsigned char>(paramArrayLength);
                    nread = socket->Receive(paramArray);

                    // parse param
                    auto paramString = System::Text::Encoding::UTF8->GetString(paramArray);
                    this->receivedParams = NetworkParamCollection::fromString(paramString);

                    // parse data(looping)
                    nread = socket->Receive(lengthArray);
                    int dataLength = *interior_ptr<int>(&lengthArray[0]);
                    this->receivedData = gcnew array<unsigned char>(dataLength);

                    if (receivedParams->existKey("Response") && (String ^)receivedParams["Response"] == "Error"
                        && this->thread_processData_keepAlive)
                    {
                        Function::InvokeSync(eventDispatcher->onError, gcnew Exception("SPlayer server response error."));
                        goto _close;
                    }

                    this->paramReceived = true;
                    if (this->thread_processData_keepAlive)
                    {
                        Function::InvokeSync(eventDispatcher->onParamReceived);
                    }

                    this->receivedDataSize = 0;
                    while (this->thread_processData_keepAlive && this->receivedDataSize < this->receivedData->Length)
                    {
                        auto newdata = gcnew array<unsigned char>(102400);
                        int nread = socket->Receive(newdata);
                        if (nread == 0)
                            break;

                        if (newdata->Length != nread)
                        {
                            auto olddata = newdata;
                            newdata = gcnew array<unsigned char>(nread);
                            Array::Copy(olddata, newdata, nread);
                        }

                        Array::Copy(newdata, 0, this->receivedData, receivedDataSize, newdata->Length);
                        receivedDataSize += newdata->Length;
                        Function::InvokeSync(eventDispatcher->onDataInProgress);
                    }
                    if (this->thread_processData_keepAlive && this->receivedDataSize == this->receivedData->Length)
                    {
                        this->dataReceived = true;
                        Function::InvokeSync(eventDispatcher->onDataReceiveFinished);
                    }
                    return;
_close:
                    this->close(2000);
                    return;

                }
                catch (ObjectDisposedException ^)
                {
                    Function::InvokeSync(eventDispatcher->onClosed);
                }
                catch (Exception ^e)
                {
                    this->thread_processData_keepAlive = false;
                    Function::InvokeSync(eventDispatcher->onError, e);
                }
            }
        protected:
            bool ^paramReceived = false;
            bool ^dataReceived = false;
            bool ^dataInProgress = false;
        public:
            /*! Params to be sent*/
            NetworkParamCollection ^sendParams = gcnew NetworkParamCollection;

            /*! Data to be sent*/
            array<unsigned char> ^sendData;

            /*! Params retrieve from server*/
            NetworkParamCollection ^receivedParams = gcnew NetworkParamCollection;

            /*! Data retrieve from server(might be incomplete before onDataReceiveFinished event*/
            array<unsigned char> ^receivedData;  

            /*! The size of data that really received in receivedData,
             *! when receivedDataSize == receivedData->Length, onDataReceiveFinished will be triggered*/
            int receivedDataSize;

            NetworkProcessorEventDispatcher ^eventDispatcher = gcnew NetworkProcessorEventDispatcher;
        private:
            Socket ^socket;
        };
    }
}
#endif // __NETWORKPROCESSOR_H__